Dinamik eklenti sistemleri oluşturmak için JavaScript Module Federation'ı keşfedin. Ölçeklenebilir ve sürdürülebilir uygulamalar için mimari, uygulama, güvenlik ve en iyi uygulamaları öğrenin.
JavaScript Module Federation Eklenti Mimarisi: Dinamik Bir Eklenti Sistemi Oluşturma
Günümüzün karmaşık web geliştirme ortamında, modüler, ölçeklenebilir ve sürdürülebilir uygulamalar oluşturmak çok önemlidir. Bunu başarmanın güçlü bir tekniği, işlevselliğin bağımsız, dinamik olarak yüklenen modüllere ayrıldığı bir eklenti mimarisidir. Webpack 5'in bir özelliği olan JavaScript Module Federation, bu tür mimarileri uygulamak için sağlam bir mekanizma sunar. Bu makale, dinamik bir eklenti sistemi oluşturmak için Module Federation kullanmanın inceliklerini ele almaktadır.
Module Federation Nedir?
Module Federation, JavaScript uygulamalarının çalışma zamanında kodu dinamik olarak paylaşmasına olanak tanır. Bu, bir uygulamadan gelen bir modülün (bir kod parçası) başka bir uygulama tarafından yeniden derlenmeye veya dağıtılmaya gerek kalmadan doğrudan kullanılabileceği anlamına gelir. Bu, farklı derlemeler ve hatta farklı dağıtımlar arasında modüllerin sunulması ve tüketilmesiyle sağlanır.
Npm paketleri gibi geleneksel kod paylaşım yöntemleri, paylaşılan bir bağımlılık güncellendiğinde tüketen uygulamaların yeniden derlenmesini ve dağıtılmasını gerektirir. Module Federation bu ek yükü ortadan kaldırarak sık güncellemelerin ve bağımsız dağıtımların gerekli olduğu senaryolar için ideal hale getirir.
Eklenti Mimarileri için Neden Module Federation Kullanılmalı?
Module Federation, eklenti mimarileri oluştururken çeşitli avantajlar sunar:
- Dinamik Modül Yükleme: Eklentiler çalışma zamanında yüklenebilir ve kaldırılabilir, bu da uygulamaların tam bir yeniden dağıtım gerektirmeden değişen gereksinimlere uyum sağlamasına olanak tanır.
- Ayrıştırma (Decoupling): Eklentiler bağımsız olarak geliştirilir ve dağıtılır, bu da uygulamanın farklı bölümleri arasındaki bağımlılıkları azaltır.
- Ölçeklenebilirlik: Uygulama, mevcut işlevselliği etkilemeden yeni eklentilerle kolayca genişletilebilir.
- Sürdürülebilirlik: Eklentiler bağımsız olarak güncellenebilir ve bakımı yapılabilir, bu da ana uygulamaya hata ekleme riskini azaltır.
- Kod Yeniden Kullanımı: Eklentiler birden fazla uygulamada yeniden kullanılabilir, bu da tutarlılığı artırır ve geliştirme çabasını azaltır.
- Sürümleme ve Geri Almalar: Eklentilerin farklı sürümlerini yönetebilir ve gerekirse önceki sürümlere kolayca geri dönebilirsiniz.
Temel Kavramlar: Ana (Host) ve Uzak (Remote) Konteynerler
Module Federation iki temel kavram etrafında döner:
- Ana Konteyner (Host Container): Uzak modülleri (eklentileri) tüketen ana uygulama.
- Uzak Konteyner (Remote Container): Ana uygulama tarafından tüketilecek modülleri (eklentileri) sunan uygulama.
Ana konteyner, sunulan modüllerin bir manifestosunu içeren uzak giriş dosyasını uzak konteynerden dinamik olarak alır. Ana uygulama daha sonra bu modüllere kendi kod tabanının bir parçasıymış gibi erişebilir ve kullanabilir.
Module Federation ile Dinamik Bir Eklenti Sistemi Uygulama: Adım Adım Kılavuz
Module Federation kullanarak basit bir eklenti sistemi oluşturma sürecini adım adım inceleyelim. Bir ana uygulama ve bir uzak eklenti uygulaması oluşturacağız.
1. Ana Uygulamayı Kurma (Host Container)
Öncelikle yeni bir proje dizini oluşturun ve yeni bir npm projesi başlatın:
mkdir host-app
cd host-app
npm init -y
Webpack ve bağımlılıklarını kurun:
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
`host-app` dizininde aşağıdaki yapılandırmayla bir `webpack.config.js` dosyası oluşturun:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const path = require('path');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
devServer: {
port: 3000,
hot: true,
static: {
directory: path.join(__dirname, 'dist'),
},
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'Host',
remotes: {
'plugin': 'Plugin@http://localhost:3001/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
Açıklama:
- `name`: Ana uygulamanın adı.
- `remotes`: Ana uygulamanın tüketeceği uzak konteynerleri tanımlar. Bu durumda, `http://localhost:3001/remoteEntry.js` adresinden `plugin` adlı bir uzak konteyneri tüketiyor. `Plugin@` sözdizimi, uzak konteynerin ModuleFederationPlugin `name` özelliğinin 'Plugin' olduğu anlamına gelir.
- `shared`: Ana ve uzak konteynerler arasında paylaşılan bağımlılıkları listeler. Bu, bu bağımlılıkların birden fazla kopyasının yüklenmesini önler. `shared` kullanmak, hatalardan kaçınmak ve uygun eklenti işlevselliğini sağlamak için kritik öneme sahiptir.
Bir `src` dizini oluşturun ve aşağıdaki içerikle bir `index.js` dosyası ekleyin:
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
const PluginComponent = React.lazy(() => import('plugin/PluginComponent'));
const App = () => {
return (
<div>
<h1>Host Application</h1>
<Suspense fallback={<div>Loading Plugin...</div>}>
<PluginComponent />
</Suspense>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Açıklama:
- `plugin` uzak konteynerinden `PluginComponent`'i dinamik olarak içe aktarmak için `React.lazy` kullanıyoruz. Bu, eklentiyi tembel yüklemek (lazy loading) ve başlangıçtaki yükleme gecikmelerini önlemek için çok önemlidir.
- `Suspense` bileşeni, eklenti alınırken yükleme durumunu yönetmek için kullanılır.
Bir `public` dizini oluşturun ve aşağıdaki içerikle bir `index.html` dosyası ekleyin:
<!DOCTYPE html>
<html>
<head>
<title>Host Application</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
</html>
Bir Babel yapılandırma dosyası `.babelrc` ekleyin:
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
`package.json` dosyanızı bir başlangıç betiğiyle güncelleyin:
{
"name": "host-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"babel-loader": "^9.1.3",
"html-webpack-plugin": "^5.6.0",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
2. Uzak Uygulamayı Kurma (Eklenti Konteyneri)
Eklenti için yeni bir proje dizini oluşturun:
mkdir plugin-app
cd plugin-app
npm init -y
Webpack ve bağımlılıklarını kurun:
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
`plugin-app` dizininde aşağıdaki yapılandırmayla bir `webpack.config.js` dosyası oluşturun:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const path = require('path');
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
devServer: {
port: 3001,
hot: true,
static: {
directory: path.join(__dirname, 'dist'),
},
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'Plugin',
filename: 'remoteEntry.js',
exposes: {
'./PluginComponent': './src/PluginComponent',
},
shared: ['react', 'react-dom'],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
Açıklama:
- `name`: Uzak konteynerin (eklentinin) adı. Bu, ana uygulamanın `remotes` yapılandırmasında kullanılan adla **eşleşmelidir**.
- `filename`: Ana uygulamanın alacağı uzak giriş dosyasının adı.
- `exposes`: Uzak konteyner tarafından sunulan modülleri tanımlar. Bu durumda, `PluginComponent` modülünü sunuyoruz. './PluginComponent' anahtarı, ana uygulamanın import ifadesinde kullanılır (örn. `import('plugin/PluginComponent')`).
- `shared`: Ana uygulama ile aynı şekilde, paylaşılan bağımlılıkları listeler. Paylaşılan bağımlılıkların ve sürümlerinin ana ve uzak uygulama arasında uyumlu olması hayati önem taşır.
Bir `src` dizini oluşturun ve aşağıdaki içerikle bir `PluginComponent.jsx` dosyası ekleyin:
import React from 'react';
const PluginComponent = () => {
return (
<div style={{border: '1px solid blue', padding: '10px'}}>
<h2>Plugin Component</h2>
<p>This is a dynamically loaded plugin!</p>
</div>
);
};
export default PluginComponent;
PluginComponent'i dışa aktarmak için `src` dizininde bir `index.js` dosyası oluşturun:
import PluginComponent from './PluginComponent';
export default PluginComponent;
Bir `public` dizini oluşturun ve aşağıdaki içerikle bir `index.html` dosyası ekleyin:
<!DOCTYPE html>
<html>
<head>
<title>Plugin Application</title>
</head>
<body>
<div id="root"></div>
<script src="./bundle.js"></script>
</body>
</html>
Bir Babel yapılandırma dosyası `.babelrc` ekleyin:
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
`package.json` dosyanızı bir başlangıç betiğiyle güncelleyin:
{
"name": "plugin-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@babel/preset-react": "^7.23.3",
"babel-loader": "^9.1.3",
"html-webpack-plugin": "^5.6.0",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
3. Uygulamaları Çalıştırma
Hem ana hem de eklenti uygulamalarını kendi dizinlerinde `npm start` komutunu çalıştırarak başlatın.
Tarayıcınızda `http://localhost:3000` adresine gidin. Dinamik olarak yüklenmiş eklenti bileşenini içeren ana uygulamayı görmelisiniz.
Gelişmiş Özellikler ve Dikkat Edilmesi Gerekenler
Sürümleme ve Geri Almalar
Module Federation, sürümlemeyi destekleyerek farklı eklenti sürümlerini yönetmenize olanak tanır. Ana uygulamanın `remotes` yapılandırmasında sürüm kısıtlamaları belirtebilirsiniz. Örneğin:
remotes: {
'plugin': 'Plugin@http://localhost:3001/remoteEntry.js@1.0.0',
}
Bu, ana uygulamaya eklentinin 1.0.0 sürümünü kullanmasını söyler. Daha yeni bir sürüm mevcut olsa bile, ana uygulama açıkça güncellenene kadar belirtilen sürümü kullanmaya devam edecektir. Sağlam bir sürümleme uygulamak, kırılgan değişiklikleri önlemek ve uygulama kararlılığını sağlamak için çok önemlidir.
Güvenlikle İlgili Hususlar
Module Federation kullanırken güvenlik her şeyden önemlidir. Aşağıdakileri göz önünde bulundurun:
- Kimlik Doğrulama ve Yetkilendirme: Yalnızca yetkili kullanıcıların eklentilere erişip kullanabildiğinden emin olmak için uygun kimlik doğrulama ve yetkilendirme mekanizmalarını uygulayın.
- Kod Bütünlüğü: Uygulamaya kötü amaçlı kod enjekte edilmesini önlemek için uzak modüllerin bütünlüğünü doğrulayın. Uygulamanın kaynakları yükleyebileceği kaynakları kısıtlamak için İçerik Güvenlik Politikası (CSP) kullanmayı düşünün.
- Bağımlılık Yönetimi: Güvenlik açıklarından kaçınmak için hem ana hem de uzak konteynerlerin bağımlılıklarını dikkatli bir şekilde yönetin. Bağımlılıkları düzenli olarak en son sürümlere güncelleyin.
- Girdi Doğrulama: Enjeksiyon saldırılarını önlemek için uzak modüllerden alınan tüm verileri doğrulayın.
- CORS (Cross-Origin Resource Sharing): Ana uygulamanın eklenti uygulamasından uzak giriş dosyasına erişmesine izin vermek için CORS'u doğru şekilde yapılandırın.
Eklenti Keşfi ve Yönetimi
Daha karmaşık eklenti sistemleri için eklentileri keşfetmek ve yönetmek için bir mekanizmaya ihtiyacınız olabilir. Bu, bir eklenti kayıt defteri veya bir keşif hizmeti aracılığıyla gerçekleştirilebilir. Merkezi bir kayıt defteri, konumları, sürümleri ve bağımlılıkları dahil olmak üzere mevcut eklentiler hakkında bilgi depolayabilir. Ana uygulama daha sonra uygun eklentileri bulmak ve yüklemek için kayıt defterini sorgulayabilir.
Şu yaklaşımları göz önünde bulundurun:
- Merkezi Yapılandırma: Eklenti URL'lerini, ana uygulamanın çalışma zamanında okuduğu merkezi bir yapılandırma dosyasında (ör. bir JSON dosyası) saklayın. Bu, ana uygulamayı yeniden dağıtmadan eklentileri kolayca eklemenize, kaldırmanıza veya güncellemenize olanak tanır.
- API Tabanlı Keşif: Mevcut eklentilerin bir listesini döndüren bir API uç noktası oluşturun. Ana uygulama daha sonra bu listeyi alabilir ve eklentileri dinamik olarak yükleyebilir.
- Olay Güdümlü Mimari: Yeni eklentiler mevcut olduğunda ana uygulamayı bilgilendirmek için bir olay veri yolu veya mesaj kuyruğu kullanın. Bu, asenkron eklenti keşfine ve yüklenmesine olanak tanır.
Dinamik Yapılandırma ve Eklenti Aktivasyonu
Kullanıcıların eklentileri dinamik olarak yapılandırmasına ve etkinleştirmesine izin vermek güçlü bir özelliktir. Bu, eklenti yapılandırmalarını depolamak ve yönetmek için bir mekanizma gerektirir. Eklenti ayarlarını depolamak için bir veritabanı, bir yapılandırma dosyası veya bulut tabanlı bir yapılandırma hizmeti kullanabilirsiniz. Ana uygulama daha sonra bu ayarları çalışma zamanında okuyabilir ve eklentileri buna göre etkinleştirebilir. Eklenti yapılandırmalarını yönetmek için bir kullanıcı arayüzü sağlamayı düşünün.
Asenkron İşlemleri ve Hata Yönetimini Ele Alma
Dinamik olarak yüklenen eklentilerle çalışırken, asenkron işlemleri ve hataları zarif bir şekilde ele almak çok önemlidir. Asenkron kodu yönetmek için `async/await` veya Promise'leri kullanın. Eklenti yükleme veya yürütme sırasında oluşan hataları yakalamak ve günlüğe kaydetmek için uygun hata yönetimini uygulayın. Kullanıcıya bilgilendirici hata mesajları sağlayın. Tüm eklentilerdeki hataları izlemek için merkezi bir hata günlüğü hizmeti kullanmayı düşünün.
Kod Bölme ve Performans Optimizasyonu
Performansı optimize etmek için, uygulamayı ve eklentileri daha küçük parçalara ayırmak için kod bölme (code splitting) kullanın. Bu, tarayıcının yalnızca belirli bir sayfa veya özellik için gereken kodu indirmesine olanak tanır. Webpack, kod bölme için yerleşik destek sağlar. Eklentileri yalnızca ihtiyaç duyulduğunda yüklemek için tembel yükleme (lazy loading) kullanmayı düşünün. Dosya boyutunu küçültmek için kodu küçültün ve sıkıştırın.
Test ve Sürekli Entegrasyon
Doğru çalıştığından emin olmak için eklenti sisteminizi kapsamlı bir şekilde test edin. Birim testleri, entegrasyon testleri ve uçtan uca testler yazın. Kod her değiştiğinde testleri otomatik olarak çalıştırmak için bir sürekli entegrasyon (CI) sistemi kullanın. Uygulamanın ve eklentilerin dağıtımını otomatikleştirmek için bir sürekli teslimat (CD) ardışık düzeni uygulayın.
Gerçek Dünya Örnekleri ve Kullanım Senaryoları
Module Federation, aşağıdakiler de dahil olmak üzere çeşitli gerçek dünya uygulamalarında kullanılmaktadır:
- E-ticaret Platformları: Ürün önerilerini, ödeme ağ geçitlerini ve kargo sağlayıcılarını dinamik olarak yükleme. Örneğin, küresel bir e-ticaret platformu, müşterinin konumuna göre farklı ödeme sağlayıcılarını entegre etmek için Module Federation'ı kullanabilir. Kuzey Amerika'da Stripe için bir eklenti yüklerken, Avrupa'da PayPal veya Klarna için bir eklenti yükleyebilir.
- İçerik Yönetim Sistemleri (CMS): Kullanıcıların CMS'nin işlevselliğini genişletmek için eklentiler yüklemesine ve etkinleştirmesine olanak tanıma. Bir CMS, kullanıcıların SEO optimizasyonu, sosyal medya entegrasyonu veya içerik analitiği için eklentiler yüklemesine izin verebilir.
- Gösterge Tabloları ve Analitik Platformları: Farklı widget'ları ve görselleştirmeleri dinamik olarak yükleme. Küresel bir analitik platformu, Google Analytics, Adobe Analytics veya Salesforce gibi farklı veri kaynakları için eklentiler yükleyebilir.
- Microfrontend Mimarileri: Büyük ölçekli web uygulamalarını bağımsız olarak dağıtılabilen mikro ön yüzlerin bir koleksiyonu olarak oluşturma. Büyük bir kurumsal şirket, web uygulamasını her biri hesap yönetimi, ürün kataloğu veya sipariş işleme gibi belirli bir iş işlevinden sorumlu olan mikro ön yüzlerin bir koleksiyonu olarak oluşturmak için Module Federation'ı kullanabilir.
- Tasarım Sistemleri: Kullanıcı arayüzü bileşenlerini ve tasarım belirteçlerini birden çok uygulama arasında paylaşma. Birden çok markası olan küresel bir kuruluş, tüm uygulamalarında ortak bir tasarım sistemini paylaşmak, tutarlılığı sağlamak ve geliştirme çabasını azaltmak için Module Federation'ı kullanabilir.
Module Federation ile Dinamik Eklenti Sistemleri Oluşturmak için En İyi Uygulamalar
Module Federation ile dinamik eklenti sistemleri oluştururken akılda tutulması gereken bazı en iyi uygulamalar şunlardır:
- Eklentileri Küçük ve Odaklı Tutun: Her eklenti belirli bir işlevsellikten sorumlu olmalıdır. Bu, eklentilerin bakımını ve güncellenmesini kolaylaştırır.
- Açık Eklenti Arayüzleri Tanımlayın: Eklentilerin ana uygulamayla nasıl etkileşime girdiğine dair açık arayüzler tanımlayın. Bu, eklentilerin ana uygulama ile uyumlu olmasını sağlar ve kırılgan değişiklikleri önler.
- Anlamsal Sürümleme Kullanın: Eklentilerinizin sürümlerini yönetmek için anlamsal sürümleme (semantic versioning) kullanın. Bu, değişiklikleri izlemeyi ve uyumluluğu sağlamayı kolaylaştırır.
- Belgelendirme Sağlayın: Eklentileriniz için açık ve özlü belgeler sağlayın. Bu, kullanıcıların eklentileri nasıl kuracaklarını, yapılandıracaklarını ve kullanacaklarını anlamalarına yardımcı olur.
- Güvenlik En İyi Uygulamalarını Uygulayın: Uygulamanızı ve eklentilerinizi güvenlik açıklarından korumak için güvenlik en iyi uygulamalarını takip edin.
- Eklenti Performansını İzleyin: Herhangi bir darboğazı belirlemek için eklentilerinizin performansını izleyin. Performansı artırmak için kodu optimize edin.
- Dağıtımı Otomatikleştirin: Uygulamanızın ve eklentilerinizin dağıtımını otomatikleştirin. Bu, hata riskini azaltır ve güncellemelerin hızlı bir şekilde dağıtılmasını sağlar.
- Tutarlı bir Kodlama Stili Kullanın: Tüm eklentilerde tutarlı bir kodlama stili uygulayın. Bu, kodun okunmasını ve bakımını kolaylaştırır.
- Birim Testleri Yazın: Eklentilerinizin doğru çalıştığından emin olmak için birim testleri yazın.
- Bir Linter Kullanın: Kodunuzu hatalara karşı otomatik olarak kontrol etmek için bir linter kullanın.
Sonuç
JavaScript Module Federation, dinamik eklenti sistemleri oluşturmak için güçlü ve esnek bir mekanizma sunar. Module Federation'dan yararlanarak, değişen gereksinimlere uyum sağlayabilen modüler, ölçeklenebilir ve sürdürülebilir uygulamalar oluşturabilirsiniz. Bu makalede özetlenen en iyi uygulamaları takip ederek, kuruluşunuzun ihtiyaçlarını karşılayan sağlam ve güvenli eklenti sistemleri oluşturabilirsiniz.
Bu teknoloji, özellikle uluslararası bağlamlarda değerlidir ve işletmelerin tamamen ayrı uygulamalar dağıtmadan yazılım tekliflerini belirli bölgelere veya müşteri segmentlerine göre uyarlamasına olanak tanır. Yerel ödeme ağ geçitlerini entegre etmekten bölgeye özgü içerik sunmaya kadar, Module Federation küresel olarak daha kişiselleştirilmiş ve verimli bir kullanıcı deneyimini kolaylaştırır.